package haven.javafx.module.base;

import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;

import java.io.IOException;
import java.net.URL;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static java.util.ResourceBundle.getBundle;
import static java.util.concurrent.CompletableFuture.supplyAsync;

/**
 *
 * spring
 * @author haven
 * see afterburner.fx
 */
public abstract class FXMLView {

    public final static String DEFAULT_ENDING = "View";
    private SimpleObjectProperty<Object> presenterProperty;
    private URL resource;
    private String bundleName;
    private ResourceBundle bundle;
    protected FXMLLoader fxmlLoader;
    protected static Executor FX_PLATFORM_EXECUTOR = Platform::runLater;
    protected final static ExecutorService PARENT_CREATION_POOL = getExecutorService();

    SpringContext springContext;


    public FXMLView() {
        this.init(getFXMLName());
    }

    public void getViewAsync(Consumer<Parent> consumer) {
        Supplier<Parent> supplier = this::getView;
        CompletableFuture.supplyAsync(supplier, PARENT_CREATION_POOL).
                thenAcceptAsync(consumer, FX_PLATFORM_EXECUTOR).
                exceptionally(this::exceptionReporter);

    }
    private void init(final String conventionalName) {
        this.presenterProperty = new SimpleObjectProperty<>();
        this.resource = getClass().getResource(conventionalName);
        this.bundleName = getBundleName();
        this.bundle = getResourceBundle(bundleName);
        this.springContext=SpringContext.getInstance();

    }

    public Parent getView() {
        this.initializeFXMLLoader();
        Parent parent = fxmlLoader.getRoot();
        addCSSIfAvailable(parent);
        return parent;
    }

    public void getView(Consumer<Parent> consumer) {
        Supplier<Parent> supplier = this::getView;
        supplyAsync(supplier, FX_PLATFORM_EXECUTOR).
                thenAccept(consumer).
                exceptionally(this::exceptionReporter);
    }
    public Node getViewWithoutRootContainer() {
        final ObservableList<Node> children = getView().getChildrenUnmodifiable();
        if (children.isEmpty()) {
            return null;
        }
        return children.listIterator().next();
    }
    public Object getPresenter() {
        this.initializeFXMLLoader();
        return this.presenterProperty.get();
    }
    void addCSSIfAvailable(Parent parent) {
        URL uri = getClass().getResource(getStyleSheetName());
        if (uri == null) {
            return;
        }
        String uriToCss = uri.toExternalForm();
        parent.getStylesheets().add(uriToCss);
    }
    void initializeFXMLLoader() {
        if (this.fxmlLoader == null) {
            this.fxmlLoader = this.loadSynchronously(resource, bundle, bundleName);
            this.presenterProperty.set(this.fxmlLoader.getController());
        }
    }
    String getStyleSheetName() {
        return getResourceCamelOrLowerCase(false, ".css");
    }
    FXMLLoader loadSynchronously(final URL resource, ResourceBundle bundle, final String conventionalName) throws IllegalStateException {
        final FXMLLoader loader = new FXMLLoader(resource, bundle);
        if(springContext!=null){
            loader.setControllerFactory(springContext);
        }
        try {
            loader.load();
        } catch (IOException ex) {
            throw new IllegalStateException("Cannot load " + conventionalName, ex);
        }
        return loader;
    }
    final String getFXMLName() {
        return getResourceCamelOrLowerCase(true, ".fxml");
    }

    String getResourceCamelOrLowerCase(boolean mandatory, String ending) {
        String name = getConventionalName(true, ending);
        URL found = getClass().getResource(name);
        if (found != null) {
            return name;
        }
        System.err.println("File: " + name + " not found, attempting with camel case");
        name = getConventionalName(false, ending);
        found = getClass().getResource(name);
        if (mandatory && found == null) {
            final String message = "Cannot load file " + name;
            System.err.println(message);
            System.err.println("Stopping initialization phase...");
            throw new IllegalStateException(message);
        }
        return name;
    }
    protected String getConventionalName(boolean lowercase, String ending) {
        return getConventionalName(lowercase) + ending;
    }
    protected String getConventionalName(boolean lowercase) {
        final String clazzWithEnding = this.getClass().getSimpleName();
        String clazz = stripEnding(clazzWithEnding);
        if (lowercase) {
            clazz = clazz.toLowerCase();
        }
        return clazz;
    }
    String getBundleName() {
        String conventionalName = getConventionalName(true);
        return this.getClass().getPackage().getName() + "." + conventionalName;
    }

    static String stripEnding(String clazz) {
        if (!clazz.endsWith(DEFAULT_ENDING)) {
            return clazz;
        }
        int viewIndex = clazz.lastIndexOf(DEFAULT_ENDING);
        return clazz.substring(0, viewIndex);
    }

    public static ResourceBundle getResourceBundle(String name) {
        try {
            return getBundle(name);
        } catch (MissingResourceException ex) {
            return null;
        }
    }
    public ResourceBundle getResourceBundle() {
        return this.bundle;
    }
    static ExecutorService getExecutorService() {
        return Executors.newCachedThreadPool((r) -> {
            Thread thread = Executors.defaultThreadFactory().newThread(r);
            String name = thread.getName();
            thread.setName("afterburner.fx-" + name);
            thread.setDaemon(true);
            return thread;
        });
    }

    /**
     *
     * @param t exception to report
     * @return nothing
     */
    public Void exceptionReporter(Throwable t) {
        System.err.println(t);
        return null;
    }

}
